8.1 数据库安全性定义
先回答一个问题:一台跑着 MySQL 的服务器被人拖走了硬盘,里面的数据会泄露吗? 答案是「会」。所以数据库安全不是一个开关,而是一层层叠起来的防线。
安全性涉及的层面
数据库安全性是个系统工程,不只是数据库管理员的事,至少涉及以下七个方面:
安全性控制的五层方法
具体到数据库自身,安全控制有五种常用手段,从外到内层层把守:
| 层次 | 手段 | 作用 |
|---|---|---|
| ① 连接层 | 用户登录 | 验证「你是谁」——账号密码、IP 白名单、双因素验证 |
| ② 操作系统层 | 文件权限 | 数据库文件本身要靠操作系统保护,不能随便被读 |
| ③ 权限层 | 权限管理 | 验证「你能做什么」——能读哪些表、能不能改 |
| ④ 数据层 | 数据加密 | 就算拿到文件,看到的也是密文 |
| ⑤ 日志层 | 安全审计 | 所有操作留痕,事后可查、可溯源 |
用户标识与鉴定
一个人要进入数据库系统,第一关就是回答「你是谁」。系统通过「用户凭证」来确认身份:
- 用户名 / 用户标识符:最基本的身份凭证
- 用户名 + 口令(密码):目前最常用的方式
- 辅助信息:手机短信验证码、IP 地理位置、口令卡、生物特征
用户存取权限控制
权限的层次
- 库权限:能否操作整个数据库
- 表权限:能否操作某张表
- 视图权限:能否查询某个视图
- 字段权限:能否查/改某个字段
视图:另一种限权方式
除了直接授权,还可以「为不同的人造不同的窗口」。 比如老师只看得到自己班的学生表,做法不是改权限,而是建一个只包含本班学生的视图, 把视图的查询权限给老师。这样从根上就限定了访问范围。
文件加密与安全审计
为什么还需要文件加密?
前面四层全是「在 MySQL 还活着的时候」起作用。如果有人绕过 MySQL,
直接拷走 .ibd 数据文件,前面这一切都白搭——文件本身是裸的。
安全审计
对于敏感的关键数据(如成绩、薪资、医疗记录),要记录「谁、在什么时候、对哪条数据做了什么」。 这些记录就是审计日志。事后哪怕真出了问题,也能倒推出来。 日常分析这些日志,还能发现潜在的攻击征兆(比如某账号深夜频繁尝试登录)。
8.2 MySQL 权限系统
你输入 mysql -u root -p,按下回车的那一秒,MySQL 内部到底干了什么?
答案藏在它的两道关卡里。
权限管理机制:两道关卡
核对账号、密码、来源 IP
每条 SQL 都要核对权限
第一关:登录验证
MySQL 收到登录请求后,去 mysql.user 表里查:
这个用户名 + 这个来源主机的组合存不存在?密码对不对?三项任何一项不匹配,登录就失败。
第二关:权限检查
登录成功之后,用户每发一条 SQL,MySQL 都要核对一次权限。
比如执行 SELECT * FROM teaching.t,MySQL 会按「全局 → 库 → 表 → 字段」的顺序查权限表,
只要某一级有权限就放行。
权限管理的五张表
权限信息分散在 mysql 数据库下的五张表里,按颗粒度从粗到细:
| 颗粒度 | 权限表 | 记录什么 |
|---|---|---|
| 全局(最粗) | mysql.user |
用户身份 + 全服务器范围的权限 |
| 数据库级 | mysql.db |
用户在某个数据库上的权限 |
| 表级 | mysql.tables_priv |
用户在某张表上的权限 |
| 字段级(最细) | mysql.columns_priv |
用户对某张表某个字段的权限 |
| 例程级 | mysql.procs_priv |
用户对存储过程/函数的权限 |
CREATE USER、GRANT、REVOKE 命令,
本质上都是在改这五张表里的记录。不同的命令,影响不同的表。这一点搞清楚了,
后面所有命令的「为什么这样写」就都明白了。
mysql.user 表的关键字段
- 身份字段:
User(用户名,主码之一)、Host(来源主机,主码之一)、authentication_string(密码哈希) - 全局权限字段:
Select_priv、Insert_priv、Update_priv等一系列 _priv 后缀的字段,每个对应一种权限,值为 Y/N - 资源限制字段:
max_questions、max_updates等,限制每小时的操作次数 - SSL 安全字段:是否要求加密连接
User + Host 一起才是主键。这意味着 'teacher1'@'localhost' 和
'teacher1'@'%' 是两个不同的用户——同一个用户名从不同地方登录,是分开管的。
用户 'admin'@'192.168.1.5' 登录后想查询 teaching.t 表,
MySQL 会按什么顺序查权限表?
mysql.user 里就有 Select_priv = Y(全局查询权限),
那查任何库任何表都不用再问。如果全局没有,就再看库级、表级、字段级。
任一级匹配上就放行——所以选 C。
8.3 MySQL 用户管理
用户管理就五件事:建、查、改名、改密、删。下面每一个都给完整命令,复制就能跑。
CREATE USER 权限的账号 登录后执行。
连接命令:mysql -u root -p,回车后输入 root 密码进入交互界面。
① 添加用户:CREATE USER
完整语法
CREATE USER [IF NOT EXISTS] '用户名'[@'主机地址或标识']
[IDENTIFIED [WITH auth_plugin] BY '密码' | RANDOM PASSWORD]
[WITH resource_option ...]
[password_option];
IF NOT EXISTS:如果用户已存在就跳过,避免报错'用户名'@'主机':必须用单引号;%表示任意主机,localhost表示本机IDENTIFIED BY '密码':设置明文密码(MySQL 自动加密存储)RANDOM PASSWORD:让 MySQL 随机生成密码并打印出来WITH MAX_QUERIES_PER_HOUR n:每小时查询次数上限
例 1:创建一个最简单的本地用户
-- 创建用户 student,密码 student123,只允许从本机登录
CREATE USER IF NOT EXISTS 'student'@'localhost'
IDENTIFIED BY 'student123';
例 2:一次创建多个用户
-- 同时创建两个用户:
-- teacher1 允许从任何主机连过来(% 表示任意IP)
-- teacher2 只允许从指定IP登录,密码由系统随机生成
CREATE USER 'teacher1'@'%' IDENTIFIED BY 'teacher123',
'teacher2'@'192.168.1.23' IDENTIFIED BY RANDOM PASSWORD;
例 3:带资源限制的用户
-- 创建 teacher5,主机不限
-- 限制:每小时最多查询 60 次、更新 100 次
CREATE USER 'teacher5'@'%'
IDENTIFIED BY 'teacher5pwd'
WITH MAX_QUERIES_PER_HOUR 60
MAX_UPDATES_PER_HOUR 100;
- 用户名和主机的引号是单引号,不是反引号也不是双引号
- 不写
@'主机'时,MySQL 默认@'%'(允许所有主机)。生产环境建议显式指定,避免账号被外网爆破 - 不要把密码写成明文存进表里——直接用
IDENTIFIED BY,MySQL 会自动加密
① 补充:直接 INSERT 系统表(了解即可)
因为 CREATE USER 本质就是往 mysql.user 表插记录,理论上可以直接 INSERT:
-- 直接往 mysql.user 表插入记录创建用户
-- 字段顺序:host, user, 密码哈希, ssl 类型, ssl 密钥, x509 颁发者, x509 主体
INSERT INTO mysql.user
(host, user, authentication_string,
ssl_type, ssl_cipher, x509_issuer, x509_subject)
VALUES('localhost', 'teacher6', SHA('t6123'), '', '', '', '');
-- 必须刷新权限缓存,否则新用户无法登录
FLUSH PRIVILEGES;
- 容易写错字段、写错值,搞坏权限表
- 必须手动
FLUSH PRIVILEGES才能生效 - 新版 MySQL 的密码加密算法不只是 SHA,自己拼可能不兼容
CREATE USER。这里讲只是为了让你理解原理。
② 查看用户
列出所有用户
-- 查询 mysql.user 表的用户名和主机信息
SELECT User, Host FROM mysql.user;
查看某个用户的详细配置
-- 查看 teacher1 的用户名、主机、每小时最大查询次数
SELECT User, Host, max_questions
FROM mysql.user
WHERE User = 'teacher1';
| User | Host | max_questions |
|---|---|---|
| teacher1 | % | 0 |
max_questions = 0 表示无限制(默认值)。
查看当前登录的是谁
-- 这两个函数在调试权限问题时非常有用
SELECT CURRENT_USER(), USER();
③ 修改用户口令
MySQL 提供了多种改密码的方式,推荐用 ALTER USER,其他几种了解即可。
方式 1:ALTER USER(推荐)
-- 把 student@localhost 的密码改成 student123
ALTER USER 'student'@'localhost' IDENTIFIED BY 'student123';
方式 2:SET PASSWORD
-- 给指定用户改密码
SET PASSWORD FOR 'student'@'localhost' = 'student123';
-- 不写 FOR 子句则修改当前登录用户自己的密码
SET PASSWORD = 'newpwd456';
方式 3:mysqladmin 命令(在系统 shell 里执行,不是在 mysql 里)
# 注意:这是 Windows/Linux 命令行命令,不是 SQL 语句
# 执行后会先要求输入旧密码,再要求输入新密码
mysqladmin -u student -p password
方式 4:UPDATE 系统表(不推荐)
UPDATE mysql.user
SET authentication_string = SHA('student123')
WHERE User = 'student' AND Host = 'localhost';
-- 直接改表必须刷新权限
FLUSH PRIVILEGES;
ALTER USER 是 MySQL 8 推荐的标准做法。
它会自动用当前的加密插件,不需要 FLUSH PRIVILEGES,也不会因为加密算法升级而失效。
④ 重命名用户:RENAME USER
-- 完整语法:可同时改用户名和主机信息
RENAME USER '原用户名'@'原主机' TO '新用户名'@'新主机';
-- 例:把 teacher1 重命名为 teacher10
-- 同时把允许登录的主机从 % (任意) 改为 localhost (仅本机)
RENAME USER 'teacher1'@'%' TO 'teacher10'@'localhost';
- 需要
mysql数据库的UPDATE权限,或服务器级CREATE USER权限 - 如果该用户被设置为某个视图、存储过程的
DEFINER,MySQL 会拒绝重命名 - 原用户的所有权限会随用户名一起迁移,不需要重新授权
⑤ 删除用户:DROP USER
-- 删除单个用户
DROP USER 'student'@'localhost';
-- 一次删除多个用户
DROP USER 'teacher5'@'%', 'teacher6'@'localhost';
-- 用户不存在时不报错
DROP USER IF EXISTS 'student'@'localhost';
DROP USER 会同时删除该用户的所有权限。删错了不可恢复,
生产环境务必先确认 SHOW GRANTS FOR 看清这个用户的权限范围再操作。
下列哪个语句能创建一个用户名为 guest、密码为 g123,
且只允许从本机登录的用户?
@'localhost' 限定来源主机,IDENTIFIED BY 设密码。
C 中 % 表示任意主机,与「只允许从本机登录」不符。
D 没有 NEW USER 这种语法。
8.4 MySQL 权限管理
建好用户只是开始。新建的用户除了能登录,几乎啥都干不了——它需要被「授权」。 权限管理三件事:授(GRANT)、看(SHOW GRANTS)、收(REVOKE)。
权限的四个层次
授权时先要想清楚:你要给的是「哪一级」的权限?
| 级别 | ON 子句写法 | 影响的表 |
|---|---|---|
| 全局(服务器级) | *.* | mysql.user |
| 数据库级 | db_name.* | mysql.db |
| 表级 | db_name.table_name | mysql.tables_priv |
| 字段级 | db_name.table_name + 列名(在权限后括号里) | mysql.columns_priv |
| 例程级 | db_name.routine_name | mysql.procs_priv |
① 授予权限:GRANT
完整语法
GRANT 权限名称 [(字段列表)] [, 权限名称 [(字段列表)]] ...
ON 授权层次及对象
TO '用户名'@'主机' [, '用户名'@'主机'] ...
[WITH GRANT OPTION];
- 权限名称:SELECT、INSERT、UPDATE、DELETE、ALL 等等
- ON 子句:作用对象,按级别有
*.*/db.*/db.tbl几种写法 - TO 子句:授给谁,可以同时给多个用户
- WITH GRANT OPTION:被授权的人能不能再把这个权限转授给别人
例 1:授予全局所有权限(最大)
-- 给 teacher10 服务器上的所有权限,并允许它再把权限转授给别人
GRANT ALL
ON *.*
TO 'teacher10'@'localhost'
WITH GRANT OPTION;
例 2:授予全局的部分权限
-- 给 teacher6 服务器级别的查询、插入、更新权限(不允许转授)
GRANT SELECT, INSERT, UPDATE
ON *.*
TO 'teacher6'@'localhost';
例 3:授予数据库级权限
-- 给 teacher4 在 teaching 库上的查询、建表、插入权限
-- ON teaching.* 表示 teaching 库下的所有表/视图
GRANT SELECT, CREATE, INSERT
ON teaching.*
TO 'teacher4'@'localhost';
例 4:授予表级权限
-- 给 teacher3 在 teaching.t 表上的增删改查权限
GRANT SELECT, INSERT, UPDATE, DELETE
ON teaching.t
TO 'teacher3'@'localhost';
例 5:授予字段级权限(最细)
-- 只允许 teacher2 查询 t 表的 tno 和 tn 两个字段
-- 字段名写在权限名后面的括号里
GRANT SELECT(tno, tn)
ON teaching.t
TO 'teacher2'@'192.168.1.23';
SELECT、INSERT、UPDATE。
其他权限(如 DELETE)必须授给整张表,因为「只删除某些列」在 SQL 里是无意义的。
CREATE USER,再 GRANT。
② 查看权限:SHOW GRANTS
-- 查看指定用户的所有权限
SHOW GRANTS FOR 'student'@'localhost';
-- 查看当前登录用户自己的权限
SHOW GRANTS FOR CURRENT_USER;
-- 上面这条还可以简写为
SHOW GRANTS;
+--------------------------------------------------------+ | Grants for student@localhost | +--------------------------------------------------------+ | GRANT USAGE ON *.* TO `student`@`localhost` | | GRANT SELECT ON `teaching`.* TO `student`@`localhost` | +--------------------------------------------------------+
GRANT USAGE ON *.* 永远会出现,它表示「这个用户存在,可以登录,但没有任何实质权限」。
这是 MySQL 用来记录「用户存在」的占位行,不用专门给。
③ 回收权限:REVOKE
语法和 GRANT 几乎对称,只是 TO 换成 FROM,没有 WITH GRANT OPTION。
REVOKE 权限名称 [(字段列表)] ...
ON 回收权限的层次及对象
FROM '用户名'@'主机';
例:回收 SELECT 和 DELETE 权限
-- 收回 teacher10 在 teaching 库上的 SELECT 和 DELETE 权限
REVOKE SELECT, DELETE
ON teaching.*
FROM 'teacher10'@'localhost';
一次回收所有权限
-- 一键收回该用户的所有权限和转授权(用户本身还在)
REVOKE ALL PRIVILEGES, GRANT OPTION
FROM 'teacher10'@'localhost';
④ 权限转移:WITH GRANT OPTION
授权的时候在末尾加上 WITH GRANT OPTION,
被授权的用户就有了「再授权给别人」的能力。这就是权限转移。
-- 给 teacher3 在 teaching.t 上的 SELECT 和 UPDATE 权限
-- 同时允许 teacher3 把这两个权限再授给别的用户
GRANT SELECT, UPDATE
ON teaching.t
TO 'teacher3'@'localhost'
WITH GRANT OPTION;
8.5 MySQL 角色管理
想象一所学校有 200 个老师,每个都要查教师表、查成绩表、改自己班的成绩。 难道要把同样的几条 GRANT 重复执行 200 遍?角色就是为了解决这个痛点。
角色是什么
有角色:定义一个
teacher 角色,给它 5 条 GRANT。然后把这个角色「戴」给 200 个老师。
要改权限只改角色就行,所有老师跟着变。
MySQL 8 开始支持角色机制。一个用户可以同时被分配多个角色,一个角色也可以分给多个用户。
角色的生命周期
从创建到撤销,一个角色经历五个阶段:
CREATE ROLE
GRANT 权限 TO 角色
GRANT 角色 TO 用户
SET DEFAULT ROLE
REVOKE / DROP ROLE
SET DEFAULT ROLE 让角色生效。这是初学者最常踩的坑。
第 1 步:创建角色
-- 语法(角色和用户长得很像,都是 名字@主机 格式)
CREATE ROLE '角色名'[@'主机'] [, '角色名'[@'主机']] ...;
-- 例 1:一次创建两个全局可用的角色
CREATE ROLE 'teacher', 'administrator';
-- 例 2:创建只能在本机生效的角色
CREATE ROLE 'developer'@'localhost';
第 2 步:给角色授权
给角色授权的语法和给用户授权一模一样——把 TO 后面的用户名换成角色名即可。
-- 例 1:管理员角色,授予所有权限
GRANT ALL PRIVILEGES
ON *.*
TO 'administrator';
-- 例 2:教师角色,授予 teaching.sc 表的增删改查
GRANT SELECT, INSERT, UPDATE, DELETE
ON teaching.sc
TO 'teacher';
PRIVILEGES,不是 PREVILEGE 也不是 PREVILEGES。
PPT 上有个手误,注意别照着写。
第 3 步:把角色分配给用户
-- 语法:GRANT 角色 TO 用户
GRANT '角色名' TO '用户名'@'主机';
-- 把 teacher 角色分配给 teacher2 用户
GRANT 'teacher' TO 'teacher2'@'%';
第 4 步:激活角色
-- 语法:让指定用户的指定角色在登录时自动激活
SET DEFAULT ROLE '角色名' TO '用户名'@'主机';
-- 例 1:让 teacher2 的 teacher 角色生效
SET DEFAULT ROLE 'teacher' TO 'teacher2'@'%';
-- 例 2:让 teacher3 拥有的所有角色都生效
SET DEFAULT ROLE ALL TO 'teacher3'@'%';
查看当前生效的角色
-- 用户登录后查询当前生效的角色
SELECT CURRENT_ROLE();
| CURRENT_ROLE() |
|---|
| `teacher`@`%` |
第 5 步:撤销与删除
从某个用户身上回收角色
-- 语法:REVOKE 角色 FROM 用户
-- 注意被收的是「角色」,从「用户」身上拿走
REVOKE '角色名' FROM '用户名'@'主机';
-- 例:把 teacher 角色从 teacher2 身上拿走
REVOKE 'teacher' FROM 'teacher2'@'%';
错误写法:
REVOKE 'teacher2' FROM 'teacher';(这会被解释为「从 teacher 角色上回收 teacher2」,逻辑错乱)正确写法:
REVOKE 'teacher' FROM 'teacher2'@'%';
彻底删除一个角色
-- 把整个角色从系统里删掉,所有绑定关系自动解除
DROP ROLE 'teacher';
下列关于 MySQL 角色的说法,错误的是?
SET DEFAULT ROLE 让角色激活,权限才真正生效。否则用户登进去会发现「明明给我赋角色了,怎么没权限」。
本章小结
- 数据库安全有五层防线:登录、文件、权限、加密、审计。本章主攻「权限」这一层。
- MySQL 用五张表存权限:user、db、tables_priv、columns_priv、procs_priv,从粗到细。所有 GRANT/REVOKE 本质都是改这五张表。
- 用户管理用
CREATE USER / ALTER USER / DROP USER这三件套,记住「用户名@主机」是联合主键。 - 权限管理用
GRANT … ON … TO …三段式,WITH GRANT OPTION决定能不能转授。回收用REVOKE … ON … FROM …,唯一区别就是 TO 换成 FROM。 - 角色是权限的集合,五步走:CREATE ROLE → GRANT 权限给角色 → GRANT 角色给用户 → SET DEFAULT ROLE 激活 → REVOKE / DROP。第四步最容易忘。
综合练习
哪条语句能创建一个用户名 dev、密码 dev2025,可以从任意主机登录的用户?
% 表示任意。C 写成 localhost 就只能本机登录。
执行 GRANT SELECT(name, age) ON school.student TO 'reader'@'%'; 后,
reader 用户能做什么?
(name, age) 限定了字段范围,所以这是「字段级 SELECT 权限」,
只能查 name 和 age 两列。
下列哪一组操作可以让 'mary'@'localhost' 真正拥有 analyst 角色定义的所有权限?
CREATE ROLE 'analyst';GRANT 'analyst' TO 'mary'@'localhost';GRANT 权限 TO 'analyst',再 GRANT 'analyst' TO 'mary'@'localhost',最后 SET DEFAULT ROLE 'analyst' TO 'mary'@'localhost'
SET DEFAULT ROLE 'analyst' TO 'mary'@'localhost'已知 'tom'@'%' 用户已存在。要让 tom 在 sales 数据库的所有表上有查询权限,
并且 能把查询权限转授给别人,正确的语句是?
C 写成
sales 而不是 sales.*,会被解释为表名而非数据库。D 用了
FROM,那是回收语法。B 正确。
管理员执行了 REVOKE INSERT ON db1.* FROM 'jerry'@'%';,
但 jerry 仍然可以往 db1 中的表插入数据。最可能的原因是?
*.*)有 INSERT,或被授予了某个含 INSERT 的角色,
那库级 REVOKE 就「白回收了」。排查时一定要 SHOW GRANTS FOR 'jerry'@'%' 把所有授权看全。